package com.liming.auth.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.liming.auth.mapper.UserAuthMapper;
import com.liming.auth.service.UserActivityService;
import com.liming.auth.service.UserAuthService;
import com.liming.common.constant.RedisConstants;
import com.liming.common.constant.TimeConstants;
import com.liming.common.enums.ActiveEnum;
import com.liming.common.enums.DateScopeEnum;
import com.liming.common.enums.LockedEnum;
import com.liming.common.enums.OperationTypeEnum;
import com.liming.common.exception.BusinessException;
import com.liming.common.exception.LoginException;
import com.liming.common.pojo.dto.authinfo.UserActivityAddDTO;
import com.liming.common.pojo.dto.authinfo.UserPageQuery;
import com.liming.common.pojo.dto.authinfo.UsernameLoginDTO;
import com.liming.common.pojo.dto.authinfo.mini.UpdateUserInfoDTO;
import com.liming.common.pojo.dto.authinfo.mini.UpdateUserPwdDTO;
import com.liming.common.pojo.entity.BaseEntity;
import com.liming.common.pojo.entity.authinfo.User;
import com.liming.common.pojo.vo.PageVO;
import com.liming.common.pojo.vo.authinfo.UserVO;
import com.liming.common.pojo.vo.authinfo.mini.MiniUserInfoVO;
import com.liming.common.pojo.vo.dashboard.UserRegister;
import com.liming.common.util.*;
import com.liming.common.util.mybatisplus.MPUtil;
import com.liming.service.redis.RedisClient;
import com.liming.service.wx.WxClient;
import kotlin.collections.ArrayDeque;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.liming.common.constant.SystemConstants.*;

/**
 * 描述
 * z
 *
 * @Author Admin
 * @Date 2025/1/8
 */
@Slf4j
@Service
@AllArgsConstructor
public class UserAuthServiceImpl extends ServiceImpl<UserAuthMapper, User>
        implements UserAuthService, MPUtil<User> {

    private final RedisClient redisClient;
    private final UserActivityService userActivityService;
    private final WxClient wxClient;

    @Override
    public List<UserRegister> registerDateCount(String scope) {
        // 上月 本周 本月
        TimeUtils.LocalDateTimeRange range = TimeUtils.getByDateScopeEnum(DateScopeEnum.getByScope(scope));
        LocalDateTime start = range.getStart();
        LocalDateTime end = range.getEnd();
        // 在开始时间和结束时间之间注册的用户
        List<User> userList = lambdaQuery().ge(User::getCreateTime, start).le(User::getCreateTime, end).list();
        Map<LocalDate, Long> dateCountMap = userList.stream()
                .collect(Collectors.groupingBy(user -> user.getCreateTime().toLocalDate(), Collectors.counting()));
        List<UserRegister> result = new ArrayDeque<>();
        for (LocalDate date = start.toLocalDate(); !date.isAfter(end.toLocalDate()); date = date.plusDays(1)) {
            Long count = dateCountMap.getOrDefault(date, 0L);
            result.add(new UserRegister(date, count));
        }
        return result;
    }

    @Override
    public String loginWithWxMini(String code) {
        // 1. 调用微信接口获取openid
        String openid = wxClient.wxMiniLogin(code);
        Assert.notBlank(openid, () -> new LoginException("微信一键登录失败"));
        // 2. 登录成功 | 判断数据库中是否含有相同的openid ， 若有 ，直接使用该账号登录
        User user = lambdaQuery().eq(User::getOpenid, openid).one();
        // 若无 则创建新用户
        if (ObjectUtil.isNull(user)) {
            user = new User();
            user.setOpenid(openid);
            user.setUsername(TimeUtils.localDateTimeNowString());
            user.setPassword("123456");
            user.setPhone("用户暂未绑定手机号，请尽快绑定！");
            user.setAvatar("https://online-ordering-system-liming.oss-cn-hangzhou.aliyuncs.com/2025/02/15/79162552-6036-4f3b-bba7-9f4835089e2c.jpeg");
            user.setNickname(IdUtil.fastSimpleUUID());
            user.setGender(0);
            user.setActive(ActiveEnum.ACTIVE.getCode());
            user.setLocked(LockedEnum.UNLOCKED.getCode());
            Assert.isTrue(save(user), () -> new LoginException("创建用户失败"));
        } else {
            // 判断账号是否被锁定，不然无法登录（锁定用户无法登录系统）
            // 未激活用户无法使用系统
            Assert.equals(user.getLocked(), LockedEnum.UNLOCKED.getCode(), () -> new LoginException("账号被锁定"));
            // 更新用户登录时间
            User updateLoginTime = new User();
            updateLoginTime.setId(user.getId());
            updateLoginTime.setLastLoginTime(LocalDateTimeUtil.now());
            Assert.isTrue(updateById(updateLoginTime), () -> new LoginException("更新用户登录时间失败"));
        }
        String token = JwtUtils.getToken(KOI, USER, ID, String.valueOf(user.getId()));
        redisClient.valueSet(RedisConstants.USER_KEY + user.getId(), token, TimeConstants.TOKEN_EXPIRE_TIME, TimeUnit.MILLISECONDS);
        Long userId = user.getId();
        CompletableFuture.runAsync(() -> {
            UserActivityAddDTO userActivityAddDTO = new UserActivityAddDTO();
            userActivityAddDTO.setUserId(userId);
            userActivityAddDTO.setOperationType(OperationTypeEnum.LOGIN.getCode());
            userActivityService.saveActivityLog(userActivityAddDTO);
        });
        return token;
    }

    @Override
    public String loginWithUsername(UsernameLoginDTO dto) {
        log.info("用户名：{}，密码：{}", dto.getUsername(), dto.getPassword());
        // 1. 查询用户
        User user = lambdaQuery().eq(User::getUsername, dto.getUsername()).one();
        Assert.notNull(user, () -> new LoginException("用户不存在"));
        // 2.校验用户密码
        String md5Pwd = SecureUtil.md5().digestHex16(dto.getPassword());
        String dbPwd = user.getPassword();
        Assert.equals(md5Pwd, dbPwd, () -> new LoginException("密码错误"));
        // 3.校验验证码
        String captchaId = dto.getCaptchaId();
        String redisCaptcha = (String) redisClient.valueGet(RedisConstants.CAPTCHA_KEY + captchaId);
        if (ObjectUtil.isNull(redisCaptcha)) {
            throw new LoginException("验证码已经过期");
        }
        if (!redisCaptcha.equalsIgnoreCase(dto.getCaptcha())) {
            throw new LoginException("验证码错误");
        }
        // 4.判断账号是否被锁定，不然无法登录（锁定用户无法登录系统）
        // 未激活用户无法使用系统
        Assert.equals(user.getLocked(), LockedEnum.UNLOCKED.getCode(), () -> new LoginException("账号被锁定"));
        // 5.更新用户登录时间
        User updateLoginTime = new User();
        updateLoginTime.setId(user.getId());
        updateLoginTime.setLastLoginTime(LocalDateTimeUtil.now());
        Assert.isTrue(updateById(updateLoginTime), () -> new LoginException("更新用户登录时间失败"));
        // 6.Redis 设置 Token
        String token = JwtUtils.getToken(KOI, USER, ID, String.valueOf(user.getId()));
        redisClient.valueSet(RedisConstants.USER_KEY + user.getId(), token, TimeConstants.TOKEN_EXPIRE_TIME, TimeUnit.MILLISECONDS);
        CompletableFuture.runAsync(() -> {
            UserActivityAddDTO userActivityAddDTO = new UserActivityAddDTO();
            userActivityAddDTO.setUserId(user.getId());
            userActivityAddDTO.setOperationType(OperationTypeEnum.LOGIN.getCode());
            userActivityService.saveActivityLog(userActivityAddDTO);
        });
        return token;
    }

    @Override
    public Boolean logout() {
        String id = ThreadLocalUtils.get(ID);
        if (ObjectUtil.isNull(id)) {
            throw new BusinessException("id为空");
        }
        String token = (String) redisClient.valueGet(RedisConstants.USER_KEY + id);
        JwtUtils.invalid(token);
        return redisClient.delete(RedisConstants.USER_KEY + id);
    }

    @Override
    public PageVO<UserVO> adminUserPage(UserPageQuery query) {
        IPage<User> rawPage = lambdaQuery()
                .like(StrUtil.isNotBlank(query.getUsername()), User::getUsername, query.getUsername())
                .like(StrUtil.isNotBlank(query.getNickname()), User::getNickname, query.getNickname())
                .like(StrUtil.isNotBlank(query.getPhone()), User::getPhone, query.getPhone())
                .eq(ObjectUtil.isNotNull(query.getGender()), User::getGender, query.getGender())
                .eq(ObjectUtil.isNotNull(query.getActive()), User::getActive, query.getActive())
                .eq(ObjectUtil.isNotNull(query.getLocked()), User::getLocked, query.getLocked())
                .page(setPage(query));
        return PageUtils.convert(rawPage, user -> BeanUtils.toBean(user, UserVO.class));
    }

    @Override
    public Boolean updateLockedById(Long id) {
        User user = lambdaQuery().eq(User::getId, id).one();
        boolean updateFlag = lambdaUpdate()
                .eq(User::getId, id)
                .set(ObjectUtil.equal(user.getLocked(), LockedEnum.UNLOCKED.getCode()), User::getLocked, LockedEnum.LOCKED.getCode())
                .set(ObjectUtil.equal(user.getLocked(), LockedEnum.LOCKED.getCode()), User::getLocked, LockedEnum.UNLOCKED.getCode())
                .update();
        Assert.isTrue(updateFlag, () -> new BusinessException("用户锁定状态更新失败"));
        return true;
    }

    @Override
    public Boolean updateActiveById(Long id) {
        User user = lambdaQuery().eq(User::getId, id).one();
        boolean updateFlag = lambdaUpdate()
                .eq(User::getId, id)
                .set(ObjectUtil.equal(user.getActive(), ActiveEnum.UN_ACTIVE.getCode()), User::getActive, ActiveEnum.ACTIVE.getCode())
                .set(ObjectUtil.equal(user.getActive(), ActiveEnum.ACTIVE.getCode()), User::getActive, ActiveEnum.UN_ACTIVE.getCode())
                .update();
        Assert.isTrue(updateFlag, () -> new BusinessException("用户激活状态更新失败"));
        return true;
    }

    @Override
    public Map<Long, Double> activityScore() {
        // 计算所有注册用户的行为分数
        // 1. 获取所有用户
        List<Long> userIds = userIdsAndNickname().keySet().stream().toList();
        // 2. 获取这些用户的行为分数
        return userActivityService.userActivityScoreMap(userIds);
    }

    @Override
    public Long userCount() {
        return lambdaQuery().list().stream().count();
    }

    @Override
    public Map<Integer, Long> userGender() {
        return lambdaQuery().list().stream().collect(Collectors.groupingBy(User::getGender, Collectors.counting()));
    }

    @Override
    public Map<Long, String> userIdsAndNickname() {
        return lambdaQuery().list().stream().collect(Collectors.toMap(User::getId, User::getNickname));
    }

    @Override
    public Map<Long, String> idAndAvatarMapByIds(List<Long> userIds) {
        if (CollUtil.isEmpty(userIds)) {
            return MapUtil.empty();
        }
        return lambdaQuery().in(User::getId, userIds).list().stream().collect(Collectors.toMap(User::getId, User::getAvatar));
    }

    @Override
    public Map<Long, String> idAndAvatarMap() {
        return lambdaQuery().list().stream().collect(Collectors.toMap(User::getId, User::getAvatar));
    }

    @Override
    public String loginOpenid() {
        return lambdaQuery().eq(User::getId, ThreadLocalUtils.get(ID)).one().getOpenid();
    }

    @Override
    public MiniUserInfoVO info() {
        User user = lambdaQuery().eq(BaseEntity::getId, ThreadLocalUtils.get(ID)).one();
        return BeanUtils.toBean(user, MiniUserInfoVO.class);
    }

    @Override
    public Boolean updateInfo(UpdateUserInfoDTO dto) {
        boolean infoUpdate = lambdaUpdate()
                .eq(BaseEntity::getId, ThreadLocalUtils.get(ID))
                .set(StrUtil.isNotBlank(dto.getAvatar()), User::getAvatar, dto.getAvatar())
                .set(StrUtil.isNotBlank(dto.getNickname()), User::getNickname, dto.getNickname())
                .set(StrUtil.isNotBlank(dto.getUsername()), User::getUsername, dto.getUsername())
                .set(ObjectUtil.isNotNull(dto.getGender()), User::getGender, dto.getGender())
                .set(ObjectUtil.isNotNull(dto.getBirth()), User::getBirth, dto.getBirth())
                .set(StrUtil.isNotBlank(dto.getPhone()), User::getPhone, dto.getPhone())
                .set(StrUtil.isNotBlank(dto.getEmail()), User::getEmail, dto.getEmail())
                .update();
        Assert.isTrue(infoUpdate, () -> new BusinessException("用户信息更新失败"));
        return true;
    }

    @Override
    public Boolean updatePassword(UpdateUserPwdDTO dto) {
        Assert.equals(dto.getNewPwd(), dto.getNewConfirmPwd(), () -> new BusinessException("两次新密码输入不一致"));
        User dbUser = getById(ThreadLocalUtils.get(ID));
        String dbPwd = dbUser.getPassword();
        String pwd = SecureUtil.md5().digestHex16(dto.getOldPwd());
        Assert.equals(pwd, dbPwd, () -> new BusinessException("旧密码输入错误"));
        boolean updateFlag = lambdaUpdate()
                .eq(BaseEntity::getId, ThreadLocalUtils.get(ID))
                .set(User::getPassword, SecureUtil.md5().digestHex16(dto.getNewPwd()))
                .update();
        Assert.isTrue(updateFlag, () -> new BusinessException("密码更新失败"));
        return true;
    }
}
